﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.Web;
using System.Xml.Linq;


namespace SPLDSMP
{
    public class SPListDrivenSiteMapProvider : SiteMapProvider
    {
        /* By default the root node's key is the url of the server. 
         * This is problematic if we want that Url to appear inside the top navigation, so i'm making the RootNode's key something unique and random.
         * */
        const string _RootNodeKey = "ca55e860-3784-4fdc-ad61-4e9c01d08a59";

        // A class for our configurations
        class SPLDSMPConfiguration
        {
            public string ListWebUrl { get; set; }
            public string ListName { get; set; }
        }

        // ListItem class
        class ListItem
        {
            public string Url { get; set; }
            public string Title { get; set; }
            public string Description { get; set; }
            public string NavigateUrl { get; set; }
        }

        // SharePoint will cache our SiteMapProvider, so we can just use class-level variables for our cache
        SPLDSMPConfiguration _Configuration;
        Dictionary<string, SiteMapNodeCollection> _ChildNodes;

        // never write an error to the UI, always log it
        public void WriteEventLogError(Exception ex)
        {
            try
            {
                if (!EventLog.SourceExists("SPLDSMP"))
                {
                    EventLog.CreateEventSource("SPLDSMP", "Application");
                }
                else
                {
                    EventLog.WriteEntry("SPLDSMP", "Unhandled Exception occurred.\n" +
                        ex.GetType().FullName +
                        @"\n" + ex.Message +
                        @"\n" + ex.StackTrace, EventLogEntryType.Error);
                }
            }
            catch (Exception)
            {
            }
        }

        // nice to haves but not need to haves
        public override SiteMapNode GetParentNode(SiteMapNode node) { throw new NotImplementedException(); }
        public override SiteMapNode FindSiteMapNode(string key) { return null; }
        protected override SiteMapNode GetRootNodeCore() { return new SiteMapNode(this, _RootNodeKey); }

        public override SiteMapNodeCollection GetChildNodes(System.Web.SiteMapNode node)
        {
            SiteMapNodeCollection returnValue = new SiteMapNodeCollection();

            try
            {
                // standard asp.net thread safe cache pattern
                if (_Configuration == null)
                {
                    lock (this)
                    {
                        if (_Configuration == null)
                        {
                            _Configuration = GetConfiguration();
                        }
                    }
                }

                // standard asp.net thread safe cache pattern
                if (_ChildNodes == null)
                {
                    lock (this)
                    {
                        if (_ChildNodes == null)
                        {
                            _ChildNodes = GetChildNodes();
                        }
                    }
                }

                SiteMapNodeCollection childNodes = null;
                if (_ChildNodes.TryGetValue(node.Key, out childNodes))
                {
                    foreach (SiteMapNode childNode in childNodes)
                    {
                        returnValue.Add(childNode);
                    }
                }
            }
            catch (Exception ex)
            {
                WriteEventLogError(ex);
            }

            return returnValue;
        }

        // Get all items and recurse through them to build sitemapnodecollections
        private Dictionary<string, SiteMapNodeCollection> GetChildNodes()
        {
            Dictionary<string, SiteMapNodeCollection> returnValue = new Dictionary<string, SiteMapNodeCollection>();

            // WCF stuff
            var basicHttpBinding = new BasicHttpBinding();
            basicHttpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
            basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;

            var endPoint = new EndpointAddress(_Configuration.ListWebUrl + "/_vti_bin/lists.asmx");

            SPLDSMP.SharePointWebServices.ListsSoapClient listsSoapClient = new SPLDSMP.SharePointWebServices.ListsSoapClient(basicHttpBinding, endPoint);
            listsSoapClient.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
            listsSoapClient.ClientCredentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;

            var list = listsSoapClient.GetList(_Configuration.ListName);

            var allRows = listsSoapClient.GetListItems(_Configuration.ListName, null, null, null, null, XElement.Parse(@"<QueryOptions>
    <ViewAttributes Scope=""RecursiveAll""/>
</QueryOptions>"), null).Descendants().Where(n => n.Name.LocalName == "row");

            var allListItems = (from row in allRows
                                select new ListItem()
                                {
                                    Url = row.Attribute("ows_FileRef").Value.Substring(row.Attribute("ows_FileRef").Value.IndexOf("#") + 1),
                                    Title = row.Attribute("ows_SPLDSMPNodeTitle").Value,
                                    Description = row.Attribute("ows_SPLDSMPNodeDescription").Value,
                                    NavigateUrl = row.Attribute("ows_SPLDSMPNodeNavigateUrl").Value                                    
                                }).ToList();

            PopulateRootChildNodes(returnValue, list.Attribute("RootFolder").Value.TrimStart('/'), allListItems);

            return returnValue;
        }

        private void PopulateRootChildNodes(
            Dictionary<string, SiteMapNodeCollection> returnValue,
            string parentFolderUrl,
            List<ListItem> allItems)
        {
            var parentNodesChildNodes = from ListItem item in allItems
                                        where GetFolderUrlFromFileUrl(item.Url).Equals(parentFolderUrl, StringComparison.CurrentCultureIgnoreCase)
                                        select item;

            SiteMapNodeCollection siteMapCollection = null;
            if (!returnValue.TryGetValue(_RootNodeKey, out siteMapCollection))
            {
                siteMapCollection = new SiteMapNodeCollection();
                returnValue.Add(_RootNodeKey, siteMapCollection);
            }

            foreach (ListItem childItem in parentNodesChildNodes)
            {
                siteMapCollection.Add(ConvertListItemToSiteMapNode(childItem));
                PopulateChildNodes(returnValue,
                childItem,
                allItems);
            }
        }

        private void PopulateChildNodes(
            Dictionary<string, SiteMapNodeCollection> returnValue,
            ListItem parentListItem,
            List<ListItem> allItems)
        {
            var parentNodesChildNodes = from ListItem item in allItems
                                        where GetFolderUrlFromFileUrl(item.Url).Equals(parentListItem.Url, StringComparison.CurrentCultureIgnoreCase)
                                        select item;

            SiteMapNodeCollection siteMapCollection = null;
            if (!returnValue.TryGetValue((string)parentListItem.NavigateUrl, out siteMapCollection))
            {
                siteMapCollection = new SiteMapNodeCollection();
                returnValue.Add((string)parentListItem.NavigateUrl, siteMapCollection);
            }

            foreach (ListItem childItem in parentNodesChildNodes)
            {
                siteMapCollection.Add(ConvertListItemToSiteMapNode(childItem));
                PopulateChildNodes(returnValue,
                childItem,
                allItems);
            }
        }

        private string GetFolderUrlFromFileUrl(string fileUrl)
        {
            return fileUrl.Substring(0, fileUrl.LastIndexOf("/"));
        }

        private SiteMapNode ConvertListItemToSiteMapNode(ListItem childItem)
        {
            return new SiteMapNode(
                this,
                childItem.NavigateUrl,
                childItem.NavigateUrl,
                childItem.Title,
                childItem.Description);
        }

        private SPLDSMPConfiguration GetConfiguration()
        {
            XDocument configurationDocument = XDocument.Load(@"C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\Template\Layouts\SPLDSMP.xml");
            return ConvertXmlToConfiguration(configurationDocument.Document.Root);
        }

        private static SPLDSMPConfiguration ConvertXmlToConfiguration(XElement specificWebConfiguration)
        {
            return new SPLDSMPConfiguration()
            {
                ListName = specificWebConfiguration.Attribute("ListName").Value,
                ListWebUrl = specificWebConfiguration.Attribute("ListWebUrl").Value,
            };
        }
    }
}

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.1
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace SPLDSMP.SharePointWebServices
{


    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ServiceModel.ServiceContractAttribute(Namespace = "http://schemas.microsoft.com/sharepoint/soap/", ConfigurationName = "SharePointWebServices.ListsSoap")]
    public interface ListsSoap
    {

        // CODEGEN: Generating message contract since element name listName from namespace http://schemas.microsoft.com/sharepoint/soap/ is not marked nillable
        [System.ServiceModel.OperationContractAttribute(Action = "http://schemas.microsoft.com/sharepoint/soap/GetList", ReplyAction = "*")]
        SPLDSMP.SharePointWebServices.GetListResponse GetList(SPLDSMP.SharePointWebServices.GetListRequest request);

        // CODEGEN: Generating message contract since element name listName from namespace http://schemas.microsoft.com/sharepoint/soap/ is not marked nillable
        [System.ServiceModel.OperationContractAttribute(Action = "http://schemas.microsoft.com/sharepoint/soap/GetListItems", ReplyAction = "*")]
        SPLDSMP.SharePointWebServices.GetListItemsResponse GetListItems(SPLDSMP.SharePointWebServices.GetListItemsRequest request);
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
    [System.ServiceModel.MessageContractAttribute(IsWrapped = false)]
    public partial class GetListRequest
    {

        [System.ServiceModel.MessageBodyMemberAttribute(Name = "GetList", Namespace = "http://schemas.microsoft.com/sharepoint/soap/", Order = 0)]
        public SPLDSMP.SharePointWebServices.GetListRequestBody Body;

        public GetListRequest()
        {
        }

        public GetListRequest(SPLDSMP.SharePointWebServices.GetListRequestBody Body)
        {
            this.Body = Body;
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
    [System.Runtime.Serialization.DataContractAttribute(Namespace = "http://schemas.microsoft.com/sharepoint/soap/")]
    public partial class GetListRequestBody
    {

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 0)]
        public string listName;

        public GetListRequestBody()
        {
        }

        public GetListRequestBody(string listName)
        {
            this.listName = listName;
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
    [System.ServiceModel.MessageContractAttribute(IsWrapped = false)]
    public partial class GetListResponse
    {

        [System.ServiceModel.MessageBodyMemberAttribute(Name = "GetListResponse", Namespace = "http://schemas.microsoft.com/sharepoint/soap/", Order = 0)]
        public SPLDSMP.SharePointWebServices.GetListResponseBody Body;

        public GetListResponse()
        {
        }

        public GetListResponse(SPLDSMP.SharePointWebServices.GetListResponseBody Body)
        {
            this.Body = Body;
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
    [System.Runtime.Serialization.DataContractAttribute(Namespace = "http://schemas.microsoft.com/sharepoint/soap/")]
    public partial class GetListResponseBody
    {

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 0)]
        public System.Xml.Linq.XElement GetListResult;

        public GetListResponseBody()
        {
        }

        public GetListResponseBody(System.Xml.Linq.XElement GetListResult)
        {
            this.GetListResult = GetListResult;
        }
    }



    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
    [System.ServiceModel.MessageContractAttribute(IsWrapped = false)]
    public partial class GetListItemsRequest
    {

        [System.ServiceModel.MessageBodyMemberAttribute(Name = "GetListItems", Namespace = "http://schemas.microsoft.com/sharepoint/soap/", Order = 0)]
        public SPLDSMP.SharePointWebServices.GetListItemsRequestBody Body;

        public GetListItemsRequest()
        {
        }

        public GetListItemsRequest(SPLDSMP.SharePointWebServices.GetListItemsRequestBody Body)
        {
            this.Body = Body;
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
    [System.Runtime.Serialization.DataContractAttribute(Namespace = "http://schemas.microsoft.com/sharepoint/soap/")]
    public partial class GetListItemsRequestBody
    {

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 0)]
        public string listName;

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 1)]
        public string viewName;

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 2)]
        public System.Xml.Linq.XElement query;

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 3)]
        public System.Xml.Linq.XElement viewFields;

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 4)]
        public string rowLimit;

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 5)]
        public System.Xml.Linq.XElement queryOptions;

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 6)]
        public string webID;

        public GetListItemsRequestBody()
        {
        }

        public GetListItemsRequestBody(string listName, string viewName, System.Xml.Linq.XElement query, System.Xml.Linq.XElement viewFields, string rowLimit, System.Xml.Linq.XElement queryOptions, string webID)
        {
            this.listName = listName;
            this.viewName = viewName;
            this.query = query;
            this.viewFields = viewFields;
            this.rowLimit = rowLimit;
            this.queryOptions = queryOptions;
            this.webID = webID;
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
    [System.ServiceModel.MessageContractAttribute(IsWrapped = false)]
    public partial class GetListItemsResponse
    {

        [System.ServiceModel.MessageBodyMemberAttribute(Name = "GetListItemsResponse", Namespace = "http://schemas.microsoft.com/sharepoint/soap/", Order = 0)]
        public SPLDSMP.SharePointWebServices.GetListItemsResponseBody Body;

        public GetListItemsResponse()
        {
        }

        public GetListItemsResponse(SPLDSMP.SharePointWebServices.GetListItemsResponseBody Body)
        {
            this.Body = Body;
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
    [System.Runtime.Serialization.DataContractAttribute(Namespace = "http://schemas.microsoft.com/sharepoint/soap/")]
    public partial class GetListItemsResponseBody
    {

        [System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue = false, Order = 0)]
        public System.Xml.Linq.XElement GetListItemsResult;

        public GetListItemsResponseBody()
        {
        }

        public GetListItemsResponseBody(System.Xml.Linq.XElement GetListItemsResult)
        {
            this.GetListItemsResult = GetListItemsResult;
        }
    }

    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    public interface ListsSoapChannel : SPLDSMP.SharePointWebServices.ListsSoap, System.ServiceModel.IClientChannel
    {
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    public partial class ListsSoapClient : System.ServiceModel.ClientBase<SPLDSMP.SharePointWebServices.ListsSoap>, SPLDSMP.SharePointWebServices.ListsSoap
    {

        public ListsSoapClient()
        {
        }

        public ListsSoapClient(string endpointConfigurationName) :
            base(endpointConfigurationName)
        {
        }

        public ListsSoapClient(string endpointConfigurationName, string remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
        {
        }

        public ListsSoapClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
        {
        }

        public ListsSoapClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
            base(binding, remoteAddress)
        {
        }

        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
        SPLDSMP.SharePointWebServices.GetListResponse SPLDSMP.SharePointWebServices.ListsSoap.GetList(SPLDSMP.SharePointWebServices.GetListRequest request)
        {
            return base.Channel.GetList(request);
        }

        public System.Xml.Linq.XElement GetList(string listName)
        {
            SPLDSMP.SharePointWebServices.GetListRequest inValue = new SPLDSMP.SharePointWebServices.GetListRequest();
            inValue.Body = new SPLDSMP.SharePointWebServices.GetListRequestBody();
            inValue.Body.listName = listName;
            SPLDSMP.SharePointWebServices.GetListResponse retVal = ((SPLDSMP.SharePointWebServices.ListsSoap)(this)).GetList(inValue);
            return retVal.Body.GetListResult;
        }

        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
        SPLDSMP.SharePointWebServices.GetListItemsResponse SPLDSMP.SharePointWebServices.ListsSoap.GetListItems(SPLDSMP.SharePointWebServices.GetListItemsRequest request)
        {
            return base.Channel.GetListItems(request);
        }

        public System.Xml.Linq.XElement GetListItems(string listName, string viewName, System.Xml.Linq.XElement query, System.Xml.Linq.XElement viewFields, string rowLimit, System.Xml.Linq.XElement queryOptions, string webID)
        {
            SPLDSMP.SharePointWebServices.GetListItemsRequest inValue = new SPLDSMP.SharePointWebServices.GetListItemsRequest();
            inValue.Body = new SPLDSMP.SharePointWebServices.GetListItemsRequestBody();
            inValue.Body.listName = listName;
            inValue.Body.viewName = viewName;
            inValue.Body.query = query;
            inValue.Body.viewFields = viewFields;
            inValue.Body.rowLimit = rowLimit;
            inValue.Body.queryOptions = queryOptions;
            inValue.Body.webID = webID;
            SPLDSMP.SharePointWebServices.GetListItemsResponse retVal = ((SPLDSMP.SharePointWebServices.ListsSoap)(this)).GetListItems(inValue);
            return retVal.Body.GetListItemsResult;
        }
    }
}
